package com.jwcao.common.cast.context;

import com.jwcao.common.cast.operation.CastFlow;
import com.jwcao.common.scan.object.ScanClass;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;

/**
 * name: CastContext description: TODO
 *
 * @author caojingwei date: 2023/3/23 16:55
 */
public class CastContext {

    protected static final Map<Class, ScanClass> CLASS_CONTEXT = new HashMap<>();

    protected static final Map<String, List<CastFlow>> CAST_WITHOUT_ID_CONTEXT = new HashMap<>();

    protected static final Map<Class, List<CastFlow>> CAST_SOURCE_CLASS_CONTEXT = new HashMap<>();

    protected static final Map<String, CastFlow> CAST_CONTEXT = new HashMap<>();

    public static CastFlow getCast(Object sourceObject, Class targetClass,
        String id) {
        return getCastByObject(sourceObject, targetClass, id);
    }

    public static CastFlow getCastByObject(Object sourceObject,
        Class targetClass,
        String id) {
        if (Objects.isNull(sourceObject)) {
            return null;
        }
        return getCastByClass(sourceObject.getClass(), targetClass, id);
    }

    protected final static List<ScanClass> getSubclass(Class clazz) {
        return CLASS_CONTEXT.values().stream().filter(s -> clazz.isAssignableFrom(s.getClazz()))
            .collect(Collectors.toList());
    }

    public static ScanClass getClass(Class clazz) {
        return CLASS_CONTEXT.get(clazz);
    }

    public static ScanClass registerClass(Class clazz) {
        CLASS_CONTEXT.putIfAbsent(clazz, new ScanClass<>(clazz));
        return getClass(clazz);
    }

    public static ScanClass registerClass(ScanClass scanClass) {
        Class clazz = scanClass.getClazz();
        CLASS_CONTEXT.putIfAbsent(clazz, scanClass);
        return getClass(clazz);
    }

    public static CastFlow getCastByKey(String key) {
        return CAST_CONTEXT.get(key);
    }

    public static CastFlow registerCast(CastFlow castFlow) {
        String key = castFlow.getKey();
        String keyWithoutId = castFlow.getKeyWithoutId();
        CAST_CONTEXT.putIfAbsent(key, castFlow);
        castFlow = getCastByKey(key);

        List<CastFlow> castFlowList = CAST_WITHOUT_ID_CONTEXT.computeIfAbsent(keyWithoutId, k -> new ArrayList<>());
        castFlowList.add(castFlow);
        List<CastFlow> sourceClassCastFlowList = CAST_SOURCE_CLASS_CONTEXT.computeIfAbsent(
            castFlow.getSourceClass().getClazz(), k -> new ArrayList<>());
        sourceClassCastFlowList.add(castFlow);
        return castFlow;
    }

    public static void flush() {
        CAST_CONTEXT.values().forEach(c -> c.flush());
    }

    public static List<ScanClass> getSubClass(Class<?> clazz) {
        return CLASS_CONTEXT.values()
            .stream()
            .filter(subClass -> clazz.isAssignableFrom(subClass.getClazz()))
            .collect(Collectors.toList());
    }

    public static CastFlow getCastByImplClass(Class sourceClass,
        Class targetClass,
        String id) {
        String key = KeyFactory.get(sourceClass, targetClass, id);
        CastFlow castFlow = CAST_CONTEXT.get(key);
        if (castFlow == null) {
            String keyWithoutId = KeyFactory.get(sourceClass, targetClass, id);
            List<CastFlow> castFlowList = CAST_WITHOUT_ID_CONTEXT.get(keyWithoutId);
            if (CollectionUtils.isNotEmpty(castFlowList)) {
                castFlow = castFlowList.get(0);
            }
        }
        return castFlow;
    }


    public static CastFlow getCastByClass(Class sourceClass,
        Class targetClass,
        String id) {
//        List<String> key = KeyFactory.get(sourceClass, targetClass, id);
        CastFlow castFlow = getCastByImplClass(sourceClass, targetClass, id);

        //如果这个时候，还是不行，那么说明 targetClass 是泛型，针对泛型搜索
        if (castFlow == null) {

            boolean effectiveId = StringUtils.isNotBlank(id);

//            String sourceClassName = sourceClass.getName();

            List<CastFlow> castFlowList = CAST_SOURCE_CLASS_CONTEXT.get(sourceClass);

            if (CollectionUtils.isNotEmpty(castFlowList)) {

                // 这里注意 还需要是targetClass的子类
                castFlowList = castFlowList.stream()
                    .filter(c -> targetClass.isAssignableFrom(c.getTargetClass().getClazz()))
                    .collect(Collectors.toList());
                if (effectiveId) {
                    castFlow = castFlowList.stream().filter(c -> StringUtils.equals(c.getId(), id)).findAny()
                        .orElse(null);
                } else {
                    castFlow = castFlowList.get(0);
                }
            }

//            castFlow = CAST_CONTEXT.entrySet().stream().filter(e -> {
//                if (effectiveId) {
//                    return KeyFactory.match(e.getKey(), id) && KeyFactory.match(e.getKey(), sourceClassName);
//                }
//                return KeyFactory.match(e.getKey(), sourceClassName);
//            }).map(Entry::getValue).findAny().orElse(null);
        }
        //
        return castFlow;
    }

    public static Map<Class, CastFlow> getCastFlowMapByClass(List<Class> sourceClassList, Class targetClass,
        String id) {
        return sourceClassList.stream().collect(
            Collectors.toMap(Function.identity(), sourceClass -> getCastByClass(sourceClass, targetClass, id)));
    }

    public static Map<Class, CastFlow> getCastFlowMapBySubClass(Class sourceClass, Class targetClass, String id) {
        List<ScanClass> subScanClassList = getSubClass(sourceClass);
        List<Class> subClassList = subScanClassList.stream().map(c -> c.getClazz()).collect(Collectors.toList());

        return CastContext.getCastFlowMapByClass(subClassList, targetClass, id);
    }

    public static CastFlow getCastFlowByClass(Class sourceClass, Class targetClass,
        String id) {

        Map<Class, CastFlow> castFlowMap = getCastFlowMapByClass(
            Arrays.asList(sourceClass), targetClass,
            id);
        return castFlowMap.get(sourceClass);
    }


}
